package com.spring.aop;

import com.spring.constants.Constants;
import com.spring.utils.StringUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * @author SpringWang
 * @date 2021/6/2
 */
public class AdviceSupport {

    private Class<?> targetClass;

    private Object target;

    private final AopConfig aopConfig;

    private Pattern pointCutClassPattern;

    private Map<Method, Map<String, Advice>> methodCache;


    public AdviceSupport(AopConfig aopConfig) {
        this.aopConfig = aopConfig;
    }

    public boolean pointCutMatch() {
        return pointCutClassPattern.matcher(targetClass.getName()).matches();
    }

    // fixme 需不需要传targetClass？
    public Map<String, Advice> getAdvices(Method method, Class<?> targetClass) throws Exception {
        Map<String, Advice> aspectMethodMap = methodCache.get(method);
        if (aspectMethodMap == null) {
            Method mt = this.targetClass.getMethod(method.getName(), method.getParameterTypes());
            aspectMethodMap = methodCache.get(mt);
            methodCache.put(mt, aspectMethodMap);
        }
        return aspectMethodMap;
    }

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    /**
     * 解析配置pointCut
     */
    private void parse() {
        String pointCut = aopConfig.getPointCut()
                .replaceAll("\\.", "\\\\.")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");

        // public.*.com.spring.service.*impl..*(.*)
        String pointCutRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
        this.pointCutClassPattern = Pattern.compile(pointCutRegex.substring(pointCutRegex.lastIndexOf(" ") + 1));

        methodCache = new HashMap<>();
        // 匹配方法的正则
        Pattern pointCutPattern = Pattern.compile(pointCut);
        // 1.缓存回调通知
        Map<String, Method> aspectMethods = new HashMap<>();
        try {
            // 拿到切面类的class
            Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass());
            // 将切面类的方法缓存到aspectMethods
            Stream.of(aspectClass.getMethods()).forEach(method -> aspectMethods.put(method.getName(), method));

            // 2.扫描目标类方法，遍历匹配
            for (Method method : targetClass.getMethods()) {
                String methodString = method.toString();
                // 如果目标方法有抛出异常，则截取
                if (methodString.contains(Constants.THROWS)) {
                    methodString = methodString.substring(0, methodString.lastIndexOf(Constants.THROWS)).trim();
                }
                // 匹配目标方法，如果匹配上，就将缓存好的通知与它建立联系
                Matcher matcher = pointCutPattern.matcher(methodString);
                if (matcher.matches()) {
                    Map<String, Advice> adviceMap = new HashMap<>();
                    // 前置通知
                    if (!StringUtils.isEmpty(aopConfig.getAspectBefore())) {
                        Advice before = new Advice(aspectClass.getConstructor().newInstance(), aspectMethods.get(aopConfig.getAspectBefore()));
                        adviceMap.put(Constants.BEFORE, before);
                    }
                    // 后置通知
                    if (!StringUtils.isEmpty(aopConfig.getAspectAfter())) {
                        Advice after = new Advice(aspectClass.getConstructor().newInstance(), aspectMethods.get(aopConfig.getAspectAfter()));
                        adviceMap.put(Constants.AFTER, after);
                    }
                    // 异常通知
                    if (StringUtils.isEmpty(aopConfig.getAspectAfterThrowing())) {
                        Advice throwing = new Advice(aspectClass.getConstructor().newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrowing()));
                        throwing.setThrowingName(aopConfig.getAspectAfterThrowingName());
                        adviceMap.put(Constants.AFTER_THROWING, throwing);
                    }
                    // 建立关联
                    methodCache.put(method, adviceMap);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Class<?> getTargetClass() {
        return targetClass;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getTarget() {
        return target;
    }
}
